#include "dig.hh"
#include <iostream>
#include <algorithm>
#include <optional>
#include <cassert>

BoundaryType getBoundaryType(DigDirection in, DigDirection out)
{
    // assert(
    //     !(in == DigDirection::Down && out == DigDirection::Up ||
    //     in == DigDirection::Up && out == DigDirection::Down ||
    //     in == DigDirection::Left && out == DigDirection::Right ||
    //     in == DigDirection::Right && out == DigDirection::Left)
    // );

    // For in
    switch (in)
    {
    case DigDirection::Down: in = DigDirection::Up; break;
    case DigDirection::Up: in = DigDirection::Down; break;
    case DigDirection::Left: in = DigDirection::Right; break;
    case DigDirection::Right: in = DigDirection::Left; break;
    }

    int value = INT(in) | INT(out);
    return static_cast<BoundaryType>(value);
}

std::vector<std::vector<Tile>> digBoundary(const std::vector<DigPlan>& plans)
{
    // First Loop, get dig max width and height
    int minWidth {0};
    int minHeight {0};
    int maxWidth {0};
    int maxHeight {0};
    int row {0};
    int col {0};

    for (const DigPlan& plan : plans)
    {
        switch (plan.direction)
        {
        case DigDirection::Down:
            row += plan.distance;
            if (row > maxHeight) maxHeight = row;
            break;
        case DigDirection::Up:    
            row -= plan.distance;
            if (row < minHeight) minHeight = row;
            break;
        case DigDirection::Left:  
            col -= plan.distance; 
            if (col < minWidth) minWidth = col;
            break;
        case DigDirection::Right: 
            col += plan.distance; 
            if (col > maxWidth) maxWidth = col;
            break;
        }
    }

    std::size_t width { static_cast<std::size_t>(maxWidth - minWidth + 1) };
    std::size_t height { static_cast<std::size_t>(maxHeight - minHeight + 1) };
    std::size_t widthBias { static_cast<std::size_t>(0 - minWidth) };
    std::size_t heightBias { static_cast<std::size_t>(0 - minHeight) };

    // Create dig map
    std::vector<std::vector<Tile>> field {
        height, std::vector<Tile>{width}
    };

    // Dig!
    field[heightBias][widthBias].type = TileType::Boundary;
    field[heightBias][widthBias].direction = getBoundaryType(plans.back().direction, plans.front().direction);

    using Position = std::pair<std::size_t, std::size_t>;
    Position pos {heightBias, widthBias};
    Position next {heightBias, widthBias};

    auto tile = [&] (std::size_t r, std::size_t c) -> Tile& {
        return field[r][c];
    };

    Tile* lastTile { nullptr };
    DigDirection lastDirection {DigDirection::Right};

    for (std::size_t planIdx = 0; planIdx < plans.size(); ++planIdx)
    {
        const DigPlan& plan {plans[planIdx]};

        switch (plan.direction)
        {
        case DigDirection::Down:
        {
            next.first += plan.distance;
            for (std::size_t r = pos.first; r < next.first; ++r)
            {
                Tile& t {tile(r, pos.second)};
                t.type = TileType::Boundary;
                if (r != pos.first) 
                    t.direction = BoundaryType::UpDown;
                else if (planIdx != 0)
                    t.direction = getBoundaryType(lastDirection, plan.direction);
            }
            break;
        }
        case DigDirection::Up:
        {
            next.first -= plan.distance;
            for (std::size_t r = pos.first; r > next.first; --r)
            {
                Tile& t {tile(r, pos.second)};
                t.type = TileType::Boundary;
                if (r != pos.first) 
                    t.direction = BoundaryType::UpDown;
                else if (planIdx != 0)
                    t.direction = getBoundaryType(lastDirection, plan.direction);
            }
            break;
        }
        case DigDirection::Left:
        {
            next.second -= plan.distance;
            for (std::size_t c = pos.second; c > next.second; --c)
            {
                Tile& t {tile(pos.first, c)};
                t.type = TileType::Boundary;
                if (c != pos.second) 
                    t.direction = BoundaryType::LeftRight;
                else if (planIdx != 0)
                    t.direction = getBoundaryType(lastDirection, plan.direction);
            }
            break;
        }
        case DigDirection::Right:
        {
            next.second += plan.distance;
            for (std::size_t c = pos.second; c < next.second; ++c)
            {
                Tile& t {tile(pos.first, c)};
                t.type = TileType::Boundary;
                if (c != pos.second) 
                    t.direction = BoundaryType::LeftRight;
                else if (planIdx != 0)
                    t.direction = getBoundaryType(lastDirection, plan.direction);
            }
            break;
        }
        }

        pos = next;
        // std::printf("[%d, %d]\n", next.first, next.second );
        // std::cout << std::ends;
        lastDirection = plan.direction;
    }

    return field;
}

void digInterior(std::vector<std::vector<Tile>>& field)
{
    std::for_each(field.begin(), field.end(), [](std::vector<Tile>& tiles){
        bool interior {false};
        bool isUp {true};

        for (std::size_t i = 0; i < tiles.size(); ++i)
        {
            Tile& tile {tiles[i]};
            if (tile.type == TileType::None)
            {
                if (interior) tile.type = TileType::Interior;
            }
            else
            {
                switch (tile.direction.value())
                {
                case BoundaryType::UpDown: interior = !interior; break;
                case BoundaryType::LeftRight: break;

                case BoundaryType::UpRight:
                case BoundaryType::DownRight:
                {
                    if (tile.direction.value() == BoundaryType::UpRight) isUp = true;
                    else isUp = false;
                    break;
                }
                case BoundaryType::DownLeft:
                case BoundaryType::UpLeft:
                {
                    if (tile.direction.value() == BoundaryType::UpLeft)
                    {
                        if (!isUp) interior = !interior; 
                    }
                    else
                    {
                        if (isUp) interior = !interior;
                    }
                    break;
                }
                }
            }
        }
    });
}

std::size_t lavaSize(const std::vector<std::vector<Tile>>& field)
{
    std::size_t size {0};

    std::size_t rowSize {field.size()};
    std::size_t colSize {field.front().size()};

    for (std::size_t row = 0; row < rowSize; ++row)
    {
        for (std::size_t col = 0; col < colSize; ++col)
        {
            if (field[row][col].type != TileType::None) size += 1;
        }
    }

    return size;
}

void printField(const std::vector<std::vector<Tile>>& field)
{
    for (const std::vector<Tile>& tiles : field)
    {
        for (const Tile& tile : tiles)
        {
            if (tile.type == TileType::None) std::cout << '.';
            else if (tile.type == TileType::Interior) std::cout << '#';
            else 
            {
                switch (tile.direction.value())
                {
                case BoundaryType::UpDown: std::cout << '|'; break;
                case BoundaryType::LeftRight: std::cout << '-'; break;
                case BoundaryType::UpLeft: std::cout << 'J'; break;
                case BoundaryType::UpRight: std::cout << 'L'; break;
                case BoundaryType::DownLeft: std::cout << '7'; break;
                case BoundaryType::DownRight: std::cout << 'F'; break;
                }
            }
        }
        std::cout << std::endl;
    }
}

void checkField(const std::vector<std::vector<Tile>>& field)
{
    for (std::size_t row = 0; row < field.size(); ++row)
    {
        const std::vector<Tile>& line {field[row]};
        for (std::size_t col = 0; col < line.size(); ++col)
        {
            if (line[col].type != TileType::None)
            {
                if (!line[col].direction.has_value()) 
                    std::printf("[%d, %d]\n", row, col);
            }
        }
    }
}